package com.zhuiYun.project.aop;

import com.alibaba.fastjson.JSON;
import com.zhuiYun.project.annotation.LimitNumber;
import com.zhuiYun.project.api.order.entity.CommodityArr;
import com.zhuiYun.project.api.order.entity.SubmitOrderVo;
import com.zhuiYun.project.common.errorCode.CommonException;
import com.zhuiYun.project.common.errorCode.EmErrorCode;
import com.zhuiYun.project.util.RedisUtil;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.stereotype.Component;

import java.lang.reflect.Method;
import java.util.List;
import java.util.Objects;

/**
 * @author: HKY
 * @date: 2023年04月12日 11:13
 * @ClassName: LimitNumberAspect
 * @Description: 限购次数切面
 * AOP：面向切面编程，通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术。
 * @Aspect 就是把一个类定义为切面供容器读取。里面可以定义切入点和通知
 * @AllArgsConstructor:生成全参构造器
 */
@Component
@Slf4j
@Aspect
@AllArgsConstructor
public class LimitNumberAspect {
    //@Around表示使用的是环切，可以用来在调用一个具体方法前和调用后来完成一些具体的任务。
    //@annotation 用于匹配当前执行方法持有指定注解的方法；
    @Around("@annotation(limitNumber)")
    public Object aopInterceptor(ProceedingJoinPoint pjp, LimitNumber limitNumber) throws Throwable {
        //获取注解参数：请求次数范围
        int value = limitNumber.value();

        //切入点的签名对象：获取目标对象中要执行的目标方法的修饰符+ 包名+组件名(类名) +方法名
        Signature signature = pjp.getSignature();
        //方法签名：包含了被拦截方法的一些信息，如目标方法的返回类型，目标方法的参数列表信息等。
        //获取方法上的注解
        MethodSignature methodSignature  = (MethodSignature) signature;

        //获取当前执行方法
        Method targetMethod = methodSignature.getMethod();
        log.info("当前执行方法:{}",targetMethod.getName());

        //获取切入点方法上的参数列表 getParameterNames 获取所有请求的参数名称
        String[] parameterNames = ((MethodSignature) signature).getParameterNames();

        //组装幂等性唯一key
        //获取切入点参数的值
        Object[] objs = pjp.getArgs();
        SubmitOrderVo args= (SubmitOrderVo) objs[0];
        List<CommodityArr> commodityArrs = JSON.parseArray(args.getProductArr(), CommodityArr.class);
        String extApiKey = "";
        extApiKey =extApiKey.concat(args.getUserId()+":"+commodityArrs.get(0).getId()+":number");
        long number = RedisUtil.incr(extApiKey, 1);
        if (Objects.nonNull(number) && number>= value+1){
            //代理方法的返回值
            log.info("该用户已没有抢购机会");
            throw new CommonException(EmErrorCode.SECKILL_NO_EXPIRE);
        }
        //ProceedingJoinPoint只能用在around(环绕通知)中
        //环绕通知=前置+目标方法执行+后置通知，proceed方法就是用于启动目标方法执行的
        return pjp.proceed();
    }
}
